﻿using System;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.Xml;
using System.Text.RegularExpressions;

namespace SimpleRedisClinet
{
    public enum RedisDataType
    {
        None,// the key not exists
        String,// the key contains a string value
        List,// the key contains a List value
        Set,// the key contains a Set value
        ZSet,// the key contains a sorted Set value
        Hash,// the key contains a Hash value
        NoSurport
    };

    /// <summary>
    /// base class for all redis class
    /// yangmeng(hvbird@gmail.com)
    /// 2011-01-20
    /// </summary>
    public class RedisBase
    {
        private string m_ip;
        private int m_port;
        private MySocket m_sock;

        public RedisBase(string hostip, int port)
        {
            IPAddress address = IPAddress.Parse(hostip);
            m_ip = hostip;
            m_port = port;
        }

        /// <summary>
        /// ping server
        /// </summary>
        /// <returns></returns>
        public string Ping()
        {
            string cmdstr = "PING\r\n";

            ResponseClass ret = send_and_receive(cmdstr);

            return ret.Strings[0];
        }

        public string GetRandomKey()
        {
            string cmdstr = "RANDOMKEY\r\n";

            ResponseClass ret = send_and_receive(cmdstr);

            if(ret.Strings.Count>0)
                return ret.Strings[0];

            return "";
        }

        public RedisDataType GetDataType(string key)
        {
            check_key(key);

            RedisDataType rettype = RedisDataType.NoSurport;

            string cmdstr = "TYPE "+key+"\r\n";

            ResponseClass ret = send_and_receive(cmdstr);

            switch (ret.Strings[0])
            {
                case "none":
                    rettype = RedisDataType.None;
                    break;
                case "string":
                    rettype = RedisDataType.String;
                    break;
                case "list":
                    rettype = RedisDataType.List;
                    break;
                case "set":
                    rettype = RedisDataType.Set;
                    break;
                case "zset":
                    rettype = RedisDataType.ZSet;
                    break;
                case "hash":
                    rettype = RedisDataType.Hash;
                    break;
            }

            return rettype;
        }

        /// <summary>
        /// set expire seconds for a object,only effect object changed
        /// </summary>
        /// <param name="key"></param>
        /// <param name="seconds"></param>
        /// <returns></returns>
        public bool Expire(string key, int seconds)
        {
            string command = "EXPIRE " + key + " " + seconds + "\r\n";
            byte[] msg = Encoding.UTF8.GetBytes(command);


            ResponseClass ret = send_and_receive(command);

            if (ret.Number > 0)
                return true;

            return false;
        }

        /// <summary>
        /// get expire seconds, this value should always be the value you seted
        /// </summary>
        /// <param name="key"></param>
        /// <returns>return -1 if not found</returns>
        public int GetExpireSeconds(string key)
        {
            string command = "TTL " + key + "\r\n";
            byte[] msg = Encoding.UTF8.GetBytes(command);


            ResponseClass ret = send_and_receive(command);

            return ret.Number;
        }

        /// <summary>
        /// delte objects,split by whitespace
        /// </summary>
        /// <param name="keys"></param>
        /// <returns></returns>
        public int Del(string keys)
        {
            string cmdstr = "DEL " + keys + "\r\n";

            ResponseClass ret = send_and_receive(cmdstr);

            return ret.Number;
        }

        /// <summary>
        /// check weather exists
        /// </summary>
        /// <param name="key"></param>
        /// <returns>1: exists 0: not exists</returns>
        public bool Exists(string key)
        {
            string cmdstr = "EXISTS " + key + "\r\n";

            ResponseClass ret = send_and_receive(cmdstr);

            if (ret.Number > 0)
                return true;

            return false;
        }

        /// <summary>
        /// check key,throw excepiton if key is not available
        /// </summary>
        /// <param name="key"></param>
        protected void check_key(string key)
        {
            if (key.Length > 128)
                throw new Exception("redis key length must lower than 128");

            Regex r = new Regex(@"[\s]");
            if (r.IsMatch(key))
                throw new Exception("redis key cannot be allowed to contain space!");
        }

        internal ResponseClass send_and_receive(string command_str)
        {
            ResponseClass ret = null;

            int retrynum = 0;

            doretry:

            try
            {
                connect();

                simple_sender(command_str);

                ret = get_response();

                disconnect();
            }
            catch (Exception ex)
            {
                SocketPool.Instance.RemoveSocket(m_sock);
                disconnect();
                
                retrynum++;

                if (retrynum < 3)
                {
                    SocketPool.Log.write_error("retry: " + command_str.Replace("\r\n","") + " time:" + retrynum + ex.Message + ex.StackTrace);
                    goto doretry;
                }

                SocketPool.Log.write_error("retry falied: " + command_str.Replace("\r\n", "") + " time:" + retrynum + ex.Message + ex.StackTrace);
            }

            return ret;
        }

        private int simple_sender(string command_str)
        {
            byte[] msg = Encoding.UTF8.GetBytes(command_str);
            int ret = 0;


            int left = msg.Length;

            while (left > 0)
            {
                ret = m_sock.Send(msg, left, SocketFlags.None);
                left -= ret;
            }


            return ret;
        }

        private ResponseClass get_response()
        {
            int buffersize = 1;
            byte[] bytes = new byte[buffersize];
            ResponseClass ret_obj = new ResponseClass(m_sock);


            ret_obj.ReceiveData();


            // if error,releash socket
            if (ret_obj.ResponseType == ResponseCommandType.ErrorRet)
            {
                throw new Exception("socket error, please retry!");
            }
 
            return ret_obj;
        }

        protected string get_serializer_str<T>(T obj)
        {
            System.Type mytype = typeof(T);

            if (mytype.Name == "String")
            {
                return obj.ToString();
            }
            else if (mytype.BaseType.Name != "ValueType")
            {
                StringBuilder sb = new StringBuilder();
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Indent = false;
                settings.Encoding = Encoding.UTF8;
                settings.CloseOutput = false;
                settings.CheckCharacters = true;
                settings.OmitXmlDeclaration = true;

                XmlWriter xmlWriter = XmlWriter.Create(sb, settings);

                XmlSerializer xml = new XmlSerializer(typeof(T));
                xml.Serialize(xmlWriter, obj);

                xmlWriter.Close();

                return sb.ToString();
            }

            return obj.ToString();
        }

        protected T get_deserialized_obj<T>(string str)
        {
            System.Type mytype = typeof(T);


            if (mytype.Name == "String")
            {
                return (T)(object)str;
            }
            else if(mytype.BaseType.Name != "ValueType")
            {
                XmlReader xmlReader = XmlReader.Create(new StringReader(str));
                XmlSerializer xml = new XmlSerializer(typeof(T));

                T retobj = (T)xml.Deserialize(xmlReader);
                xmlReader.Close();

                return retobj;
            }

            return ConvertType<T>(str, default(T));
        }

        /// <summary>
        /// connect to redis server
        /// </summary>
        protected void connect()
        {
            if (m_sock != null)
                return;


            m_sock = SocketPool.Instance.GetSocket(m_ip, m_port);
        }

        /// <summary>
        /// close socket
        /// </summary>
        protected void disconnect()
        {
            if (m_sock != null)
            {
                m_sock.Used = false;
            }

            m_sock = null;
        }

        /// <summary>
        /// 泛型数据类型转换
        /// </summary>
        /// <typeparam name="T">自定义数据类型</typeparam>
        /// <param name="value">传入需要转换的值</param>
        /// <param name="defaultValue">默认值</param>
        /// <returns></returns>
        public static T ConvertType<T>(object value, T defaultValue)
        {
            try
            {
                return (T)ConvertToT<T>(value, defaultValue);
            }
            catch
            {
                return default(T);
            }
        }

        /// <summary>
        /// 转换数据类型
        /// </summary>
        /// <typeparam name="T">自定义数据类型</typeparam>
        /// <param name="myvalue">传入需要转换的值</param>
        /// <param name="defaultValue">默认值</param>
        /// <returns></returns>
        private static object ConvertToT<T>(object myvalue, T defaultValue)
        {
            TypeCode typeCode = System.Type.GetTypeCode(typeof(T));
            if (myvalue != null)
            {
                string value = Convert.ToString(myvalue);
                switch (typeCode)
                {
                    case TypeCode.Boolean:
                        bool flag = false;
                        if (bool.TryParse(value, out flag))
                        {
                            return flag;
                        }
                        break;
                    case TypeCode.Char:
                        char c;
                        if (Char.TryParse(value, out c))
                        {
                            return c;
                        }
                        break;
                    case TypeCode.SByte:
                        sbyte s = 0;
                        if (SByte.TryParse(value, out s))
                        {
                            return s;
                        }
                        break;
                    case TypeCode.Byte:
                        byte b = 0;
                        if (Byte.TryParse(value, out b))
                        {
                            return b;
                        }
                        break;
                    case TypeCode.Int16:
                        Int16 i16 = 0;
                        if (Int16.TryParse(value, out i16))
                        {
                            return i16;
                        }
                        break;
                    case TypeCode.UInt16:
                        UInt16 ui16 = 0;
                        if (UInt16.TryParse(value, out ui16))
                            return ui16;
                        break;
                    case TypeCode.Int32:
                        int i = 0;
                        if (Int32.TryParse(value, out i))
                        {
                            return i;
                        }
                        break;
                    case TypeCode.UInt32:
                        UInt32 ui32 = 0;
                        if (UInt32.TryParse(value, out ui32))
                        {
                            return ui32;
                        }
                        break;
                    case TypeCode.Int64:
                        Int64 i64 = 0;
                        if (Int64.TryParse(value, out i64))
                        {
                            return i64;
                        }
                        break;
                    case TypeCode.UInt64:
                        UInt64 ui64 = 0;
                        if (UInt64.TryParse(value, out ui64))
                            return ui64;
                        break;
                    case TypeCode.Single:
                        Single single = 0;
                        if (Single.TryParse(value, out single))
                        {
                            return single;
                        }
                        break;
                    case TypeCode.Double:
                        double d = 0;
                        if (Double.TryParse(value, out d))
                        {
                            return d;
                        }
                        break;
                    case TypeCode.Decimal:
                        decimal de = 0;
                        if (Decimal.TryParse(value, out de))
                        {
                            return de;
                        }
                        break;
                    case TypeCode.DateTime:
                        DateTime dt;
                        if (DateTime.TryParse(value, out dt))
                        {
                            return dt;
                        }
                        break;
                    case TypeCode.String:
                        if (!string.IsNullOrEmpty(value))
                        {
                            return value.ToString();
                        }
                        break;
                }
            }

            return defaultValue;
        }
    }
}
